
/**		
 * 
 *	uk.co.lmfm.net.subversion.SVNClient
 *	
 *	@version 1.00 | Jan 7, 2010
 *	@author Justin Windle
 *  
 **/
 
package uk.co.lmfm.net.subversion 
{
	import uk.co.lmfm.net.subversion.events.SVNClientErrorEvent;
	import uk.co.lmfm.net.subversion.events.SVNClientEvent;
	import uk.co.lmfm.net.subversion.vo.SVNLogReportVO;
	import uk.co.lmfm.net.subversion.vo.SVNRevisionVO;

	import com.adobe.net.URI;

	import org.httpclient.HttpClient;
	import org.httpclient.HttpRequest;
	import org.httpclient.events.HttpDataEvent;
	import org.httpclient.events.HttpResponseEvent;

	import mx.utils.Base64Encoder;

	import flash.events.IOErrorEvent;
	import flash.events.SecurityErrorEvent;
	import flash.utils.ByteArray;

	/**
	 * SVNClient
	 */
	public class SVNClient extends HttpClient 
	{
		//	----------------------------------------------------------------
		//	CONSTANTS
		//	----------------------------------------------------------------

		public static const HTTP_CODE_OK : String = "200";
		public static const HTTP_CODE_MULTI_STATUS : String = "207";

		public static const RESPONSE_LOG_REPORT : String = 'log-report';
		public static const RESPONSE_MULTISTATUS : String = 'multistatus';

		public static const NAMESPACE : Namespace = new Namespace('S', 'svn:');

		//	----------------------------------------------------------------
		//	PRIVATE MEMBERS
		//	----------------------------------------------------------------

		private var _username : String;
		private var _password : String;
		private var _repository : String;
		private var _connected : Boolean;
		private var _latestRevisionNumber : int;

		private var _data : ByteArray = new ByteArray();
		private var _base64Encoder : Base64Encoder = new Base64Encoder();

		//	----------------------------------------------------------------
		//	CONSTRUCTOR
		//	----------------------------------------------------------------

		public function SVNClient(__proxy : URI = null)
		{
			super(__proxy);
			configureListeners();
		}

		//	----------------------------------------------------------------
		//	PUBLIC METHODS
		//	----------------------------------------------------------------

		override public function request(uri : URI, request : HttpRequest, timeout : int = 60000) : void
		{
			if(_username && _password) authenticate(request);
			super.request(uri, request, timeout);
		}

		public function authenticate(request : HttpRequest) : void
		{
			_base64Encoder.encode(_username + ":" + _password);
			request.header.replace("Authorization", "Basic " + _base64Encoder.flush());
		}

		public function connect(__repository : String, __username : String = null, __password : String = null) : void
		{
			_repository = __repository;
			_username = __username;
			_password = __password;
			
			getLatestRevisionNumber();
		}

		public function getRevisions(startRevision : int = 0, endRevision : int = -1, path : String = "") : void
		{
			endRevision = endRevision == -1 ? startRevision : endRevision;
			
			var xml : XML = new XML('<S:log-report xmlns:S="svn:" />');
			
			xml.NAMESPACE::['start-revision'] = startRevision;			xml.NAMESPACE::['end-revision'] = endRevision;			xml.NAMESPACE::['path'] = '';
			
			xml.NAMESPACE::['discover-changed-paths'] = '';
			xml.NAMESPACE::['include-merged-revisions'] = '';			//xml.NAMESPACE::['strict-node-history'] = '';			//xml.NAMESPACE::['all-revprops'] = '';			//xml.NAMESPACE::['limit'] = 1;

			xml.addNamespace(NAMESPACE);
			
			request(new URI(_repository), new SVNRequest(SVNRequest.TYPE_REPORT, 1, xml.toXMLString()));
		}

		public function getLatestRevisionNumber() : void
		{
			request(new URI(_repository), new SVNRequest(SVNRequest.TYPE_PROPFIND, 0));
		}

		//	----------------------------------------------------------------
		//	PRIVATE METHODS
		//	----------------------------------------------------------------

		private function configureListeners() : void
		{
			addEventListener(IOErrorEvent.IO_ERROR, onIOError);			addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
			addEventListener(HttpResponseEvent.COMPLETE, onResponseComplete);
			addEventListener(HttpDataEvent.DATA, onDataComplete);
		}
		
		private function onSecurityError(event : SecurityErrorEvent) : void
		{
			trace("Security error");
		}

		private function onIOError(event : IOErrorEvent) : void
		{
			trace("IOError");
		}

		private function getDataString() : String
		{
			_data.position = 0;
			return  _data.readUTFBytes(_data.bytesAvailable);
		}

		/*private function getDataXML(includeNamespaces : Boolean = false) : XML
		{
		try
		{
		if(includeNamespaces) return new XML(getDataString());
		else return new XML(getDataString().replace(/<(.*?) xmlns[:=].*?>/ig, "<$1>").replace(/<(\/)?\w+:(.*?)>/gi, "<$1$2>"));
		}
		catch(error : Error)
		{
		trace("Error:", error.message);
		}
			
		return new XML();
		}*/

		private function parseResponse() : void
		{
			var data : SVNLogReportVO = new SVNLogReportVO();
			
			try
			{
				data.xml = new XML(getDataString().replace(/<(.*?) xmlns[:=].*?>/ig, "<$1>").replace(/<(\/)?\w+:(.*?)>/gi, "<$1$2>"));
				data.latestRevisionNumber = data.xml.descendants("version-name")[0];
			}
			catch(error : Error)
			{
				var errorEvent : SVNClientErrorEvent = new SVNClientErrorEvent(SVNClientErrorEvent.RESPONSE_PARSE_ERROR, false, false, error.message, error.errorID);
				dispatchEvent(errorEvent);
				
				return;
			}
			trace(data.xml);
			var type : String = String(data.xml.name()).toLowerCase();
			
			switch(type)
			{
				case RESPONSE_MULTISTATUS : 
				
					_latestRevisionNumber = data.latestRevisionNumber;
					
					if(_connected)
					{
						dispatchEvent(new SVNClientEvent(SVNClientEvent.GOT_LATEST_REVISION_NUMBER, data));
					}
					else
					{
						_connected = true;
						dispatchEvent(new SVNClientEvent(SVNClientEvent.CONNECTED, data));
					}
				
					break;
					
				case RESPONSE_LOG_REPORT : 
				
					var items : XMLList = data.xml.descendants('log-item');
					var added : XMLList, modified : XMLList;
					var len : int = items.length();
					var revision : SVNRevisionVO;
					var i : int, j : int, n : int;
					
					for (i = 0;i < len;i++) 
					{
						var time : Array = items[i].child('date').match(/[0-9]{2,4}/g);
						
						revision = new SVNRevisionVO();
						revision.comment = items[i].child('comment');						revision.author = items[i].child('creator-displayname');
						revision.revisionNumber = parseInt(items[i].child('version-name'));
						revision.date = new Date(time[0], time[1] - 1, time[2], time[3], time[4], time[5]);
												added = items[i].descendants('added-path');
						modified = items[i].descendants('modified-path');
						
						n = added.length();
						
						for (j = 0;j < n;j++)
						{
							revision.paths[j] = added[j];
							
							if(data.paths.indexOf(added[j]) < 1)
							{
								data.paths.push(added[j]);
							}
						}
						
						n = modified.length();
						
						for (j = 0;j < n;j++)
						{
							revision.paths[j] = modified[j];
							
							if(data.paths.indexOf(modified[j]) < 1)
							{
								data.paths.push(modified[j]);
							}
						}
						
						data.latestRevisionNumber = Math.max(revision.revisionNumber, data.latestRevisionNumber);
						data.revisions[i] = revision;
					}
					
					dispatchEvent(new SVNClientEvent(SVNClientEvent.LOG_REPORT_RECIEVED, data));
					
					break;
			}
			
			_data.length = 0;
		}

		//	----------------------------------------------------------------
		//	EVENT HANDLERS
		//	----------------------------------------------------------------

		private function onResponseComplete(event : HttpResponseEvent) : void
		{
			trace("onResponseComplete:", event.response.code, event.response.message);

			switch(event.response.code)
			{
				case HTTP_CODE_OK :
				case HTTP_CODE_MULTI_STATUS :
				
					parseResponse();
				 
					break;
			}
		}

		private function onDataComplete(event : HttpDataEvent) : void
		{
			//_data.length = 0;
			_data.writeBytes(event.bytes, 0, event.bytes.length);
			//_data.position = 0;

			trace("onDataComplete:", _data.length);
		}

		/*private function onGetLatestRevisionNumberComplete(event : SVNClientEvent) : void
		{
		_latestRevisionNumber = event.data.latestRevisionNumber;
			
		removeEventListener(SVNClientEvent.LOG_REPORT_RECIEVED, onGetLatestRevisionNumberComplete);
			
		if(!_connected)
		{
		_connected = true;
		dispatchEvent(new SVNClientEvent(SVNClientEvent.CONNECTED));
		}
		}

		private function onGetRevisionsComplete(event : SVNClientEvent) : void
		{
		removeEventListener(SVNClientEvent.LOG_REPORT_RECIEVED, onGetRevisionsComplete);
			
		trace(event.data.xml);
		}*/

		//	----------------------------------------------------------------
		//	PUBLIC ACCESSORS
		//	----------------------------------------------------------------

		public function get latestRevisionNumber() : int
		{
			return _latestRevisionNumber;
		}
	}
}